﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;

namespace SBPweb.MediaCenter.Common.Modularity
{
    public class ModuleLoader
    {

        #region Private members

        private static Dictionary<string, ConstructorInfo> sModuleConstructors;

        private DirectoryInfo pDirectory;
        private FileInfo pConfigFile;
        private AppDomain pAppDomain;
        private Dictionary<string, IModule> pModules;

        #endregion

        #region Constructor logic

        static ModuleLoader()
        {
            sModuleConstructors = new Dictionary<string, ConstructorInfo>();
        }

        public ModuleLoader(string pathOfModules, string pathOfConfigurationFile)
        {
            pDirectory = new DirectoryInfo(pathOfModules);
            if (!pDirectory.Exists)
            {
                throw new ArgumentException("Path of modules does not exist!");
            }

            pConfigFile = new FileInfo(pathOfConfigurationFile);
            if (!pConfigFile.Exists)
            {
                throw new ArgumentException("Path of configuration file does not exist!");
            }
        }

        #endregion

        #region Public properties

        public Dictionary<string, IModule> Modules
        {
            get
            {
                return pModules;
            }
        }

        #endregion

        #region Public methods

        public void Initialize(IModuleHost component, ModuleHostType hostType)
        {
            string modulesConfPath = pDirectory.FullName;

            pAppDomain = AppDomain.CreateDomain("SBPweb.MediaCenter.Modules");
            pAppDomain.AppendPrivatePath(pDirectory.FullName);

            ModulesConfiguration mC = new ModulesConfiguration(pConfigFile.FullName);

            pModules = new Dictionary<string, IModule>();
            
            foreach (FileInfo file in mC.Modules)
            {
                try
                {
                    ModuleDescriptor md = new ModuleDescriptor(file);
                    IModule module = CreateModuleInstance(md.RawData, md.ModuleTypeName, component, hostType);
                    if (module != null)
                    {
                        module.Initialize();
                        if (module.IsInitialized)
                        {
                            pModules.Add(md.ModuleTypeName, module);
                        }
                    }
                }
                catch (Exception ex)
                {
                    //TODO: do some logging
                }
            }
        }

        public void Finalize()
        {
            if (pModules != null)
            {
                foreach (IModule module in pModules.Values)
                {
                    module.Finalize();
                }

                pModules.Clear();
            }
            if (pAppDomain != null)
            {
                AppDomain.Unload(pAppDomain);
            }
        }

        #endregion

        #region Helper methods

        internal ConstructorInfo GetConstructorInfo(byte[] rawData, string moduleTypeName)
        {
            Assembly asm;

            try
            {
                asm = pAppDomain.Load(rawData);

                Type type = asm.GetType(moduleTypeName);
                return type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(IModuleHost), typeof(ModuleHostType) }, null);
            }
            catch (Exception ex)
            {
//                LogManager.WriteCritical(moduleTypeName, ex, "ModuleLoader");
                return null;
            }
        }

        public IModule CreateModuleInstance(byte[] rawData, string moduleTypeName)
        {
            return CreateModuleInstance(rawData, moduleTypeName, new object[] { });
        }

        public IModule CreateModuleInstance(byte[] rawData, string moduleTypeName, IModuleHost parent, ModuleHostType hostType)
        {
            return CreateModuleInstance(rawData, moduleTypeName, new object[] { parent, hostType });
        }

        public IModule CreateModuleInstance(byte[] rawData, string moduleTypeName, object[] args)
        {
            ConstructorInfo constructorInfo;

            lock (sModuleConstructors)
            {
                if (sModuleConstructors.ContainsKey(moduleTypeName))
                {
                    constructorInfo = sModuleConstructors[moduleTypeName];
                }
                else
                {
                    constructorInfo = GetConstructorInfo(rawData, moduleTypeName);
                    sModuleConstructors.Add(moduleTypeName, constructorInfo);
                }
            }

            IModule ret = null;

            try
            {
                ret = (IModule)constructorInfo.Invoke(args);
            }
            catch (Exception ex)
            {
//                LogManager.WriteCritical(moduleTypeName, ex, "ModuleLoader");
                return null;
            }

            return ret;
        }

        #endregion

        /*
        protected Assembly mAppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            return null;
        }

        protected Assembly mAppDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
        {
            return null;
        }
        */

    }
}